/**
 * @author Maneesh Chiba <maneesh.chiba@vanillaforums.com>
 * @copyright 2009-2024 Vanilla Forums Inc.
 * @license Proprietary
 */

import { Toast } from "@library/features/toaster/Toast";
import { toastManagerClasses } from "@library/features/toaster/ToastContext.styles";
import { useStackingContext } from "@vanilla/react-utils";
import { uuidv4 } from "@vanilla/utils";
import React, { ReactNode, useContext, useState } from "react";

export interface IToast {
    /** If the toast should dismiss itself */
    autoDismiss?: boolean;
    /** Autodismiss duration */
    autoDismissDuration?: number;
    /** If the toast should persist at the bottom of the stack */
    persistent?: boolean;
    /** If a toast should be dismissible by the user*/
    dismissible?: boolean;
    /** React body of the toast */
    body: ReactNode;
    /** Apply custom styling to the toast */
    className?: string;
    /** Use a wider toast variant */
    wide?: boolean;
    toastID?: string;
}

interface IToasterContext {
    /** List of toast currently being displayed */
    toasts: IToast[] | null;
    /** Add a new toast to the top of the stack */
    addToast(toast: IToast): string;
    /** Update a specific toast */
    updateToast(toastID: string, toast: IToast, visibility?: boolean): void;
    /** Remove a specific toast */
    removeToast(toastID: string): void;
    /** Let's apply extra zIndex when in modal */
    setIsInModal: (isInModal: boolean) => void;
}

interface IToastState extends IToast {
    /** Identifier for the toast, used for update and remove  */
    toastID?: string;
    /** Animate the toast in or out  */
    visibility?: boolean;
}

/**
 * Context for the global toast notifications
 */
export const ToastContext = React.createContext<IToasterContext>({
    toasts: null,
    addToast: () => "",
    updateToast: () => {},
    removeToast: () => {},
    setIsInModal: () => {},
});

/**
 * Hook to used to post to global toast notifications
 */
export function useToast() {
    return useContext(ToastContext);
}

export function useToastErrorHandler() {
    const toast = useToast();

    return (error: any) => {
        const toastID = toast.addToast({
            body: error?.message ?? "Something has gone wrong.",
            dismissible: true,
        });

        return toastID;
    };
}

/**
 * Toast notifications logic
 */
export function ToastProvider(props: { children: ReactNode }) {
    const { children } = props;
    const [toasts, setToast] = useState<IToastState[] | null>(null);
    const [isInModal, setToastIsInModal] = useState<boolean>();

    const addToast = (toast: IToastState) => {
        const toastID = toast.toastID ?? uuidv4();
        const newToast: IToastState = { ...toast, toastID, visibility: true };
        setToast((prevState) => {
            // We can replace the existing toast.
            if (!prevState) {
                return [newToast];
            } else {
                return [...prevState.filter((toast) => toast.toastID !== toastID), newToast];
            }
        });
        return toastID;
    };

    // Expose new toasts so regular js and legacy views can use them
    window.__LEGACY_ADD_TOAST__ = addToast;

    const updateToast = (toastID: string, updatedToast: IToast, visibility?: boolean) => {
        if (updatedToast) {
            setToast((prevState) =>
                prevState
                    ? prevState.map((prevToast) => {
                          if (prevToast.toastID === toastID) {
                              return {
                                  ...prevToast,
                                  ...updatedToast,
                                  toastID: prevToast.toastID,
                                  ...(visibility !== undefined && { visibility: visibility }),
                              };
                          }
                          return prevToast;
                      })
                    : prevState,
            );
        }
    };

    const removeToast = (toastID: string) => {
        // First set the visibility to false (to allow the exit animation)
        setToast((prevState) => {
            if (prevState) {
                return prevState.map((prevToast, index) => {
                    if (prevToast.toastID === toastID) {
                        // prevToast does not hold maintain the content of the body field,
                        // so I am spreading it directly from the state
                        return { ...prevState[index], visibility: false };
                    }
                    return prevToast;
                });
            }
            return prevState;
        });
        // Then remove the item altogether
        setTimeout(() => {
            setToast((prevState) =>
                prevState ? prevState.filter((prevToast) => prevToast.toastID !== toastID) : prevState,
            );
        }, 2000);
    };

    const setIsInModal = (isInModal: boolean) => {
        setToastIsInModal(isInModal);
    };

    return (
        <ToastContext.Provider
            value={{
                toasts,
                addToast,
                updateToast,
                removeToast,
                setIsInModal,
            }}
        >
            <ToastManager isInModal={isInModal} />
            {children}
        </ToastContext.Provider>
    );
}

declare global {
    interface Window {
        __LEGACY_ADD_TOAST__: (toast: IToast) => string;
    }
}

/**
 * Renders all notifications
 */
function ToastManager(props: { isInModal?: boolean }) {
    const classes = toastManagerClasses();
    const { toasts } = useContext(ToastContext);

    const { zIndex } = useStackingContext();

    return (
        <section className={classes.area} style={{ zIndex: props.isInModal ? zIndex + 1 : undefined }}>
            {toasts && toasts.length > 0 && (
                <>
                    {/* Render persistent toasts first */}
                    {toasts
                        .filter((toast) => toast.persistent)
                        .map((toast: IToastState) => {
                            return (
                                <Toast
                                    wide={toast.wide}
                                    key={toast.toastID}
                                    visibility={toast.visibility ?? true}
                                    autoCloseDuration={
                                        toast.autoDismiss ? toast.autoDismissDuration ?? 3000 : undefined
                                    }
                                    dismissible={toast.dismissible}
                                    className={toast.className}
                                >
                                    {toast.body}
                                </Toast>
                            );
                        })}
                    {/* Render non-persistent toasts */}
                    {toasts
                        .filter((toast) => !toast.persistent)
                        .map((toast: IToastState) => {
                            return (
                                <Toast
                                    key={toast.toastID}
                                    visibility={toast.visibility ?? true}
                                    autoCloseDuration={
                                        toast.autoDismiss ? toast.autoDismissDuration ?? 3000 : undefined
                                    }
                                    dismissible={toast.dismissible}
                                    className={toast.className}
                                >
                                    {toast.body}
                                </Toast>
                            );
                        })}
                </>
            )}
            <div id="portaled-toasts"></div>
        </section>
    );
}
